package se.webbzon.boltzmann.login;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Map.Entry;

public class HTTPMultipartPostRequest extends HTTPPostRequest {
	
	// The message used when locking the request for single data posting
	private static final String externLock = "The request is written to by an external object.";
	
	// The maximal buffer size
	private static final int maxBufferSize = 1*1024*1024;
	
	// Two hyphens and a line end (CRLF)
	private static final String twoHyphens = "--", lineEnd = "\r\n";
	
	// The data output stream associated with this multipart request
	private final DataOutputStream dos;
	
	// The boundary used for multipart postings
    private String boundary;
    
    // The index of the current auto-generated field name
    private int fieldNameIndex;
	
	/*============================================================
	Constructors
	============================================================*/

	/** Creates a new HTTP multipart post request. **/
	public HTTPMultipartPostRequest(URL url) throws IOException {
		this(url,null,null);
	}
	
	/** Creates a new HTTP multipart post request. **/
	public HTTPMultipartPostRequest(URL url, LoginAccess access) throws IOException {
		this(url,access,null);
	}
	
	/** Creates a new HTTP multipart post request. **/
	public HTTPMultipartPostRequest(URL url, HTTPCookie[] cookies) throws IOException {
		this(url,null,cookies);
	}
	
	/** Creates a new HTTP multipart post request. **/
	public HTTPMultipartPostRequest(URL url, LoginAccess access, HTTPCookie[] cookies) throws IOException {
		super(url,access,cookies);
		boundary = "*****"+Long.toString(System.currentTimeMillis())+"*****";
		fieldNameIndex = 0;
		HttpURLConnection conn = this.getHttpURLConnection();
		conn.setRequestProperty("Content-Type", "multipart/form-data; boundary="+boundary); 
		dos = new DataOutputStream(conn.getOutputStream());
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Returns an output stream which can be used to write to the multipart post request. 
	 * This method will block all other methods for this multipart post request until
	 * releaseOutputStream() is called. **/
	public OutputStream acquireOutputStream(String contentType, String name) throws IOException {
		checkLock();
		lock(externLock);
		dos.writeBytes(twoHyphens + boundary + lineEnd);
		dos.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"; filename=\"test.bass\"" + lineEnd);
		dos.writeBytes("Content-Type: " + contentType + lineEnd);
		dos.writeBytes("Content-Transfer-Encoding: binary" + lineEnd);
		dos.writeBytes(lineEnd);
		return dos;
	}
	
	/** Releases the output stream for the multipart post request. **/
	public void releaseOutputStream() throws IOException {
		dos.writeBytes(lineEnd);
		unlock();
	}
	
	@Override public void addString(String str) throws IOException {
		String name = "field" + fieldNameIndex;
		addField(name,str);
		fieldNameIndex++;
	}

	/** Adds a plain text to the HTTP post request with the given field name. **/
	public void addString(String name, String str) throws IOException {
		addField(name,str);
	}
	
	@Override public void addBytes(String contentType, byte[] b, int off, int len) throws IOException {
		String name = "field" + fieldNameIndex;
		addBytes(contentType, name, b, off, len);
		fieldNameIndex++;
	}

	/** Adds a plain text to the HTTP post request with the given field name. **/
	public void addBytes(String contentType, String name, byte[] b, int off, int len) throws IOException {
		checkLock();
		dos.writeBytes(twoHyphens + boundary + lineEnd);
		dos.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"" + lineEnd);
		dos.writeBytes("Content-Type: " + contentType + lineEnd);
		dos.writeBytes("Content-Transfer-Encoding: binary" + lineEnd);
		dos.writeBytes(lineEnd);
		dos.write(b, off, len);
		dos.writeBytes(lineEnd);
	}
	
	@Override public void addInputStream(String contentType, InputStream is, int len) throws IOException {
		String name = "field" + fieldNameIndex;
		addInputStream(contentType,name,is,len);
		fieldNameIndex++;
	}

	/** Adds a binary content with the given name to the HTTP post request from the 
	 * given input stream. The data length and a MIME content type must be provided 
	 * when calling this method. **/
	public void addInputStream(String contentType, String name, InputStream is, int len) throws IOException {
		checkLock();
		dos.writeBytes(twoHyphens + boundary + lineEnd);
		dos.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"" + lineEnd);
		dos.writeBytes("Content-Type: " + contentType + lineEnd);
		dos.writeBytes("Content-Transfer-Encoding: binary" + lineEnd);
		dos.writeBytes(lineEnd);
			
		int bufferSize = Math.min(maxBufferSize, is.available());
		byte[] buffer = new byte[bufferSize];
		int available = Math.min(len, bufferSize);
		while (available > 0) {
			is.read(buffer, 0, available);
			dos.write(buffer, 0, available);
			len -= available;
			available = Math.min(len, Math.min(maxBufferSize, is.available()));
		}

		dos.writeBytes(lineEnd);
	}

	@Override public void addFields(Map<String, String> fields) throws IOException {
		checkLock();
		String key = null, value = null;
		for (Entry<String,String> field : fields.entrySet()) try {
			key = URLEncoder.encode(field.getKey(),"UTF-8");
			value = field.getValue() == null ? "" : URLEncoder.encode(field.getValue(),"UTF-8");
			dos.writeBytes(twoHyphens + boundary + lineEnd);
			dos.writeBytes("Content-Disposition: form-data; name=\"" + key + "\"" + lineEnd);
			dos.writeBytes("Content-Type: text/plain" + lineEnd);
			dos.writeBytes(lineEnd);
			dos.writeBytes(value);
			dos.writeBytes(lineEnd);
		} catch (UnsupportedEncodingException e) {
			throw new IllegalArgumentException("An error occurred when encoding the field: " + key + "=" + value);
		} 
	}
	
	@Override public void close() throws IOException {
		dos.close();
		super.close();
	}

	@Override public HTTPResponse send(int timeout) throws IOException {
		dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
		dos.flush();
		dos.close();
		return super.send(timeout);
	}
	
	

}
